package cn.openbiotoken.android.facetoken;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import androidx.annotation.NonNull;

import com.nnf.nnfkitlib.NNF_FEATURE_TYPE;
import com.nnf.nnfkitlib.NNF_FaceInfo;
import com.nnf.nnfkitlib.NNF_Feature;
import com.nnf.nnfkitlib.NNF_Image;
import com.nnf.nnfkitlib.NNF_RESULT;

import java.util.Arrays;
import java.util.List;

import cn.openbiotoken.BiometricsFeature;
import cn.openbiotoken.android.UriBiometricsFeatureExtractor;
import cn.openbiotoken.impl.BiometricsFeatureImpl;

public class HomoSapiensUriBiometricsFeatureExtractor extends UriBiometricsFeatureExtractor<float[]>  {

    public HomoSapiensUriBiometricsFeatureExtractor(@NonNull final Context context, @NonNull final Uri uri) {
        super(context, uri);
    }

    @Override
    public final BiometricsFeature<float[]> extract() {

        RecognitionContext recognitionContext = new RecognitionContext.Builder().build();
        beforeInitializeRecognitionContext(recognitionContext);
        recognitionContext.initialize();
        Log.d(getClass().getSimpleName(), String.format("extract(): Mask Enabled = %s, Mask Recognition Enabled = %s.", recognitionContext.getFunction().FACE_MASK_ENABLED, recognitionContext.getFunction().FACE_MASK_RECOGNITION_ENABLED));
        Log.d(getClass().getSimpleName(), String.format("extract(): Extracted Bitmap Degree = %d", uriBitmapHelper.extractBitmapDegree()));

        final NNF_Image image = new NNF_Image();
        image.bitmap = uriBitmapHelper.loadBitmap();
        image.angle = 0;
        Log.d(getClass().getSimpleName(), String.format("extract(): Bitmap byte count = %d, width = %d, height = %d.", image.bitmap.getByteCount(), image.bitmap.getWidth(), image.bitmap.getHeight()));

        final NNF_FaceInfo face = new NNF_FaceInfo();
        final NNF_RESULT detectMaxFaceResult = recognitionContext.getDetector().DetectionMaxFace(recognitionContext.getSession(), image, face);
        logResult(detectMaxFaceResult, "extract", "Detect Max Face");
        Log.d(getClass().getSimpleName(), String.format("extract(): Face track ID = %d, score = %f, completeness = %d, bigAngle = %s, clarity = %d, light = %d, num = %d.", face.faceTrackID, face.faceScore, face.faceCompleteness, face.bigAngle, face.faceClarity, face.facelight, face.faceNum));

        final NNF_Feature feature = new NNF_Feature();
        feature.type = NNF_FEATURE_TYPE.DATA_FLOAT32;
        final NNF_RESULT getFeatureResult = recognitionContext.getRecognizer().GetFeature(recognitionContext.getSession(), image, face, feature);
        logResult(getFeatureResult, "extract", "Recognize Feature");
        Log.d(getClass().getSimpleName(), String.format("extract(): Feature type = %s, length = %d, binary = %s.", feature.type.name(), feature.length, Arrays.toString(feature.feature)));

        BiometricsFeature<float[]> biometricsFeature = new BiometricsFeatureImpl<>();
        biometricsFeature.setFeature(Arrays.copyOf(feature.feature, feature.length));
        Log.d(getClass().getSimpleName(), String.format("extract(), Biometrics Feature length = %d, binary = %s", biometricsFeature.getFeature().length, Arrays.toString(biometricsFeature.getFeature())));

        if (!image.bitmap.isRecycled()) {
            image.bitmap.recycle();
        }
        recognitionContext.release();

        return biometricsFeature;
    }

    protected void beforeInitializeRecognitionContext(@NonNull final RecognitionContext recognitionContext) {
        recognitionContext.getFunction().FACE_3DPOSE_ENABLED = true;
        recognitionContext.getFunction().FACE_5LANDMARKS_ENABLED = true;
        recognitionContext.getFunction().FACE_106LANDMARKS_ENABLED = true;
        recognitionContext.getFunction().FACE_AGE_ENABLED = true;
        recognitionContext.getFunction().FACE_GENDER_ENABLED = true;
    }

    @Override
    public final float compare(@NonNull final BiometricsFeature<float[]> a, @NonNull final BiometricsFeature<float[]> b) {

        @NonNull final RecognitionContext recognitionContext = new RecognitionContext.Builder().build();
        recognitionContext.initialize();

        @NonNull final NNF_Feature featureA = convertFeature(a);
        Log.d(getClass().getSimpleName(), String.format("compare(): Feature A type = %s, length = %d, binary = %s.", featureA.type.name(), featureA.length, Arrays.toString(featureA.feature)));

        @NonNull final NNF_Feature featureB = convertFeature(b);
        Log.d(getClass().getSimpleName(), String.format("compare(): Feature B type = %s, length = %d, binary = %s.", featureB.type.name(), featureB.length, Arrays.toString(featureB.feature)));

        //final float similarity = recognitionContext.getRecognizer().CompareFeature(recognitionContext.getSession(), featureA, featureB);
        final float similarity = recognitionContext.compareFeatures(featureA, featureB);
        Log.d(getClass().getSimpleName(), String.format("compare(): similarity = %f.", similarity));

        recognitionContext.getInitializer().ReleaseContext(recognitionContext.getSession());
        return similarity;
    }

    @Override
    public final BiometricsFeature<float[]> top(@NonNull final BiometricsFeature<float[]> sample, @NonNull final BiometricsFeature<float[]>[] library, final float valve) {

        @NonNull final RecognitionContext recognitionContext = new RecognitionContext.Builder().build();
        recognitionContext.initialize();

        @NonNull final NNF_Feature sampleFeature = convertFeature(sample);

        float maxSimilarity = Float.MIN_VALUE;
        BiometricsFeature<float[]> mostSimilarFeature = null;
        for (@NonNull BiometricsFeature<float[]> feature : library) {
            @NonNull final NNF_Feature nativeFeature = convertFeature(feature);
            final float similarity = recognitionContext.getRecognizer().CompareFeature(recognitionContext.getSession(), sampleFeature, nativeFeature);
            feature.putExtra(BiometricsFeature.EXTRA_KEY_SIMILARITY, similarity);
            if (similarity > maxSimilarity) {
                maxSimilarity = similarity;
                mostSimilarFeature = feature;
            }
        }

        return maxSimilarity >= valve && null != mostSimilarFeature ? mostSimilarFeature : null;
    }

    @Override
    public final BiometricsFeature<float[]> top(@NonNull BiometricsFeature<float[]> sample, @NonNull List<BiometricsFeature<float[]>> library, float valve) {

        @NonNull final RecognitionContext recognitionContext = new RecognitionContext.Builder().build();
        recognitionContext.initialize();

        @NonNull final NNF_Feature sampleFeature = convertFeature(sample);
        Log.d(getClass().getSimpleName(), String.format("top(): sample feature type = %s, length = %d, version = %s, feature = %s.", sampleFeature.type.name(), sampleFeature.length, sampleFeature.version, Arrays.toString(sampleFeature.feature)));

        float maxSimilarity = Float.MIN_VALUE;
        BiometricsFeature<float[]> mostSimilarFeature = null;
        for (@NonNull BiometricsFeature<float[]> feature : library) {
            @NonNull final NNF_Feature nativeFeature = convertFeature(feature);
            Log.d(getClass().getSimpleName(), String.format("top(): loop feature type = %s, length = %d, version = %s, feature = %s.", nativeFeature.type.name(), nativeFeature.length, nativeFeature.version, Arrays.toString(nativeFeature.feature)));
            final float similarity = recognitionContext.compareFeatures(sampleFeature, nativeFeature);
            Log.d(getClass().getSimpleName(), String.format("top(): similarity = %f.", similarity));
            feature.putExtra(BiometricsFeature.EXTRA_KEY_SIMILARITY, similarity);
            if (similarity > maxSimilarity) {
                maxSimilarity = similarity;
                mostSimilarFeature = feature;
            }
        }

        return maxSimilarity >= valve && null != mostSimilarFeature ? mostSimilarFeature : null;
    }

    @NonNull
    protected final NNF_Feature convertFeature(@NonNull final BiometricsFeature<float[]> feature) {
        @NonNull final NNF_Feature nativeFeature = new NNF_Feature();
        nativeFeature.feature = feature.getFeature();
        nativeFeature.length = nativeFeature.feature.length;
        nativeFeature.type = NNF_FEATURE_TYPE.DATA_FLOAT32;
        nativeFeature.version = "g1";
        return nativeFeature;
    }

    protected final void logResult(@NonNull final NNF_RESULT result, @NonNull final String method, @NonNull final String what) {
        Log.println(NNF_RESULT.FSUCCESS == result ? Log.DEBUG : Log.ERROR, getClass().getSimpleName(), String.format("%s(): %s Result number = %d, name = %s.", method, what, result.getValue(), result.name()));
    }
}
